using System;
using System.IO;
using System.Linq;
using System.Text;
using Microsoft.Win32;
using System.Threading;
using System.Collections;
using System.Diagnostics;
using System.Windows.Forms;
using System.Collections.Generic;

namespace Publish.Services
{
    /// <summary>
    /// 文件及文件操作类
    /// </summary>
    public class FileService
    {
        #region Field


        #endregion

        #region Constructor

        public FileService()
        {

        }

        #endregion

        #region Delegate

        /// <summary>
        /// 完成一个代理
        /// </summary>
        /// <param name="sender"></param>
        public delegate void OnPublishHander(String sender);

        /// <summary>
        /// 显示当前正在发布文件
        /// </summary>
        public event OnPublishHander ShowCurrentItem;

        #endregion

        #region Public Method

        /// <summary>
        /// 复制选中文件
        /// </summary>
        /// <param name="parent"></param>
        /// <param name="desktopFolders"></param>
        /// <param name="isUpdate">是否仅仅更新项目文件</param>
        /// <param name="publishFileList">发布文件，用于记录到数据库</param>
        public void CopyDirectory(TreeNode parent, String desktopFolders, Boolean isUpdate, ref ArrayList publishFileList)
        {
            TreeNode node = parent;
            if (node == null) return;

            if (node.Checked)
            {
                var split = node.Tag.ToString().Split('|');//文件类型标识

                if (split[0] == "file")//只复制文件
                {
                    var directoryName = split[1] + node.Text;//文件所处项目相对路径

                    if (!isUpdate)//仅仅执行更新，不发布文件
                    {
                        var path = node.FullPath.Substring(0, node.FullPath.LastIndexOf('\\') + 1).Replace(Publish.ProjectName, "");
                        var deskTop = desktopFolders + path;
                        var target = new DirectoryInfo(deskTop);//源码路径
                        if (!target.Exists) target.Create();//目标路径不见鸟~~~给他创建一个

                        var sourcePath = Publish.Path.Substring(0, Publish.Path.LastIndexOf('\\') + 1) + node.FullPath;//待复制文件，源文件
                        var targetPath = deskTop + node.Text;//目标文件

                        File.Copy(sourcePath, targetPath, true); //复制文件

                        publishFileList.Add(directoryName);//记录每个被选中发布文件
                    }

                    #region 更新发布文件

                    var currentFiles = new Files
                    {
                        ProjectName = Publish.ProjectName,
                        LastWriteTime = DateTime.Parse(node.Name),//记录文件现在修改时间
                        Size = Decimal.Parse(node.ToolTipText),//记录文件现在大小
                        Path = directoryName,
                        Type = Enum.CrudType.C,//新增属性
                    };

                    var key = PublishFileVersionService.GetKey(directoryName);
                    List<Files> filesList;

                    if (Publish.DictionaryList.TryGetValue(key, out filesList)) //有值
                    {
                        var beforeFiles = filesList.FirstOrDefault(x => x.Path == directoryName);
                        if (beforeFiles == null) //新增
                        {
                            Publish.DictionaryList[key].Add(currentFiles);
                        }
                        else//更新
                        {
                            currentFiles.Type = Enum.CrudType.U;//更新属性

                            for (var i = 0; i < Publish.DictionaryList[key].Count; i++)
                            {
                                var item = Publish.DictionaryList[key][i];
                                if (item.Path != currentFiles.Path) continue;

                                Publish.DictionaryList[key][i] = currentFiles; //更新文件列表
                                break;
                            }
                        }
                    }
                    else Publish.DictionaryList[key] = new List<Files> { currentFiles };//新增

                    #endregion
                }
            }

            if (node.FirstNode != null)//如果node节点还有子节点则进入遍历
            {
                CopyDirectory(node.FirstNode, desktopFolders, isUpdate, ref publishFileList);
            }
            if (node.NextNode != null)//如果node节点后面有同级节点则进入遍历
            {
                CopyDirectory(node.NextNode, desktopFolders, isUpdate, ref publishFileList);
            }
        }

        /// <summary>
        /// 选择性复制文件及文件夹
        /// </summary>
        /// <param name="srcDir">目标地址</param>
        /// <param name="tgtDir">生成地址</param>
        public void CopyDirectory(String srcDir, String tgtDir)
        {
            var source = new DirectoryInfo(srcDir);//目标路径（项目所在路径）
            var target = new DirectoryInfo(tgtDir);//源码路径

            if (target.FullName.StartsWith(source.FullName, StringComparison.CurrentCultureIgnoreCase))
            {
                MessageBox.Show("父目录不能拷贝到子目录！", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
                return;
            }

            if (!source.Exists) return;//原路径找不到鸟~~~

            if (!target.Exists) target.Create();//目标路径不见鸟~~~给他创建一个

            var fileList = source.GetFiles();//获取文件

            foreach (var fileInfo in fileList)
            {
                if (Publish.FilesExtension.Contains(fileInfo.Extension)) continue;//判断是否为排除的文件类型，若是则跳过该类型文件

                if (Publish.Files.Contains(fileInfo.Name)) continue;//判断是否为排除文件，若是则跳过该文件

                fileInfo.IsReadOnly = false;//因为文件是从VS里面直接复制过来的，处于只读状态

                ShowCurrentItem(fileInfo.Name);//显示正在复制的文件名称

                File.Copy(fileInfo.FullName, target.FullName + "/" + fileInfo.Name, true);//复制文件

                #region 记录当前文件版本信息，用于下次判断是否被修改，以此推断是否需要发布

                var directoryName = (fileInfo.DirectoryName ?? String.Empty).Replace(Publish.Path, Publish.ProjectName) + "\\" + fileInfo.Name;//请注意：为了缩减字符串长度，涉及两处修改时需保持结果一致

                var currentFiles = new Files
                {
                    ProjectName = Publish.ProjectName,
                    LastWriteTime = fileInfo.LastWriteTime,
                    Size = fileInfo.Length,
                    Path = directoryName,
                    Type = Enum.CrudType.C,//新增属性
                };

                var path = currentFiles.Path;
                var key = PublishFileVersionService.GetKey(path);

                var beforeFiles = Publish.DictionaryList.ContainsKey(key) ? Publish.DictionaryList[key].FirstOrDefault(x => x.Path == path) : null;

                if (beforeFiles == null)//文件版本信息不存在，创建
                {
                    if (!Publish.DictionaryList.ContainsKey(key))//创建一个键
                        Publish.DictionaryList[key] = new List<Files>();

                    Publish.DictionaryList[key].Add(currentFiles);
                    continue;
                }

                if (beforeFiles.LastWriteTime == currentFiles.LastWriteTime && beforeFiles.Size == currentFiles.Size)//文件未发生改变，跳过
                    continue;

                currentFiles.Type = Enum.CrudType.U;//更新属性

                for (var i = 0; i < Publish.DictionaryList[key].Count; i++)
                {
                    var item = Publish.DictionaryList[key][i];

                    if (item.Path != path) continue;

                    Publish.DictionaryList[key][i] = currentFiles; //更新文件列表
                }

                #endregion
            }

            var dirs = source.GetDirectories();//返回路径信息

            foreach (DirectoryInfo info in dirs)
            {//通过递归复制文件夹
                if (info.GetFiles("*", SearchOption.TopDirectoryOnly).Length + info.GetDirectories("*", SearchOption.AllDirectories).Length == 0)
                {//排除空文件夹
                    continue;
                }

                if (Publish.Folder.Contains(info.Name)) continue;//是否为排除文件夹

                CopyDirectory(info.FullName, target.FullName + "/" + info.Name);//递归寻找文件夹下面的
            }
        }

        /// <summary>
        /// 删除 文件/文件夹 至回收站
        /// </summary>
        /// <param name="fullPath"></param>
        public static void DeleteToRecycleBin(String fullPath)
        {
            try
            {
                if (System.IO.File.Exists(fullPath))
                {
                    //为何不始用File.Delete()，是因为该方法不经过回收站，直接删除文件
                    //要删除至回收站，可使用VisualBasic删除文件，需引用Microsoft.VisualBasic
                    //删除确认对话框是根据电脑系统-回收站-显示删除确认对话框   是否打勾 自动添加的
                    //为何不使用c#的File.Delete()方法？？？因为该方法是直接删除，而不是放入回收站
                    Microsoft.VisualBasic.FileIO.FileSystem.DeleteFile(fullPath,
                        Microsoft.VisualBasic.FileIO.UIOption.OnlyErrorDialogs,//是否显示删除确认对话框
                        Microsoft.VisualBasic.FileIO.RecycleOption.SendToRecycleBin,
                        Microsoft.VisualBasic.FileIO.UICancelOption.DoNothing);
                }
                else if (System.IO.Directory.Exists(fullPath))
                {
                    Microsoft.VisualBasic.FileIO.FileSystem.DeleteDirectory(fullPath,
                        Microsoft.VisualBasic.FileIO.UIOption.OnlyErrorDialogs,//是否显示删除确认对话框
                        Microsoft.VisualBasic.FileIO.RecycleOption.SendToRecycleBin);
                }
            }
            catch (Exception)
            {

            }
        }

        #region WinRar压缩相关

        /// <summary>
        /// 生成Zip压缩包
        /// </summary>
        /// <param name="path">文件夹路径</param>
        /// <param name="rarPath">生成压缩文件的路径</param>
        /// <param name="rarName">生成压缩文件的文件名</param>
        public void CompressRar(String path, String rarPath, String rarName)
        {
            try
            {
                String winRarPath = null;
                if (!ExistsWinRar(out winRarPath))//验证WinRar是否安装。
                {//未安装WinRar用户使用 System.IO.Compression 压缩

                    var filePath = rarPath + rarName;

                    DeleteToRecycleBin(filePath);//删除可能存在的重名文件，防止 IOException was unhandled 异常

                    System.IO.Compression.ZipFile.CreateFromDirectory(path, rarPath + rarName
                        , System.IO.Compression.CompressionLevel.Fastest, false);

                    return;
                }

                using (var process = new Process())
                {
                    var pathInfo = String.Format("a -r -afzip -m0 -ep1 \"{0}\" \"{1}\"", rarName, path);

                    #region WinRar 用到的命令注释

                    //[a] 添加到压缩文件
                    //afzip 执行zip压缩方式，方便用户在不同环境下使用。（取消该参数则执行rar压缩）
                    //-m0 存储 添加到压缩文件时不压缩文件。共6个级别【0-5】，值越大效果越好，也越慢
                    //ep1 依名称排除主目录（生成的压缩文件不会出现不必要的层级）
                    //r   修复压缩档案
                    //t   测试压缩档案内的文件 
                    //as  同步压缩档案内容  
                    //-p  给压缩文件加密码方式为：-p123456

                    #endregion

                    //打包文件存放目录
                    process.StartInfo = new ProcessStartInfo
                                            {
                                                FileName = winRarPath, //执行的文件名
                                                Arguments = pathInfo, //需要执行的命令
                                                UseShellExecute = false, //使用Shell执行
                                                WindowStyle = ProcessWindowStyle.Hidden, //隐藏窗体
                                                WorkingDirectory = rarPath, //rar 存放位置
                                                CreateNoWindow = true, //是隐藏窗体
                                            };

                    process.Start(); //开始执行
                    process.WaitForExit(); //等待完成并退出
                }
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        /// <summary>
        /// 解压
        /// </summary>
        /// <param name="unRarPath">文件夹路径</param>
        /// <param name="rarPath">压缩文件的路径</param>
        /// <param name="rarName">压缩文件的文件名</param>
        /// <returns></returns>
        public String UnCompressRar(String unRarPath, String rarPath, String rarName)
        {
            try
            {
                String winRarPath = null;
                if (!ExistsWinRar(out winRarPath)) return ""; //验证WinRar是否安装。

                using (var process = new Process())
                {
                    if (Directory.Exists(unRarPath) == false)
                    {
                        Directory.CreateDirectory(unRarPath);
                    }

                    var pathInfo = "x " + rarName + " " + unRarPath + " -y";

                    process.StartInfo = new ProcessStartInfo
                    {
                        FileName = winRarPath, //执行的文件名
                        Arguments = pathInfo, //需要执行的命令
                        UseShellExecute = false, //使用Shell执行
                        WindowStyle = ProcessWindowStyle.Hidden, //隐藏窗体
                        WorkingDirectory = rarPath, //rar 存放位置
                        CreateNoWindow = false, //不显示窗体
                    };

                    process.Start(); //开始执行
                    process.WaitForExit(); //等待完成并退出
                }
            }
            catch (Exception ex)
            {
                throw ex;
            }
            return unRarPath;
        }

        #endregion

        /// <summary>
        /// Using 排序
        /// </summary>
        /// <param name="path">文件夹地址</param>
        public void SortUsing(String path)
        {
            var classList = new List<String>();//.cs文件集合

            GetClassList(path, ref classList);

            if (classList.Count == 0) return;

            foreach (var item in classList)
            {
                var lineList = new List<String>();//using行集合

                var fileName = item.Substring(item.LastIndexOf('\\') + 1);

                ShowCurrentItem(fileName);//显示正在操作的文件名称

                var encoding = Encoding.UTF8;

                encoding = GetFileEncodeType(item);

                #region 获取 using 引用

                Int32 line = -1;//第一行有效代码行号
                Int32 firestNamespaceCodeLine = 0;//Namespace出现的行号
                string lineText;//每行字符串

                var file = new System.IO.StreamReader(item, encoding);
                for (var i = 0; (lineText = file.ReadLine()) != null; i++)
                {
                    if (CheckUsingIsEnd(lineText)) break;//出现Namespace通常意味着Using结束了，若遇到神奇的那就是缘份了

                    if (lineText.StartsWith("using ") || String.IsNullOrWhiteSpace(lineText)) //确定是否using引用行，这里没有限定结束符是为了防止有人在引用后加注释
                    {
                        lineList.Add(lineText);

                        if (line == -1) line = i;//记录第一行有效代码行号
                    }
                }

                //while ((lineText = file.ReadLine()) != null)
                //{
                //    if (lineText.StartsWith("using ") && lineText.EndsWith(";")) //确定是否using引用行
                //    {
                //        lineList.Add(lineText);

                //        if (line == 0) {line =  }
                //    }
                //    line++;
                //}

                file.Close();
                file.Dispose();

                #endregion

                #region 根据引用长度冒泡排序

                for (var i = lineList.Count; i > 0; i--)
                {
                    if (String.IsNullOrWhiteSpace(lineList[i - 1])) continue;//跳过空行

                    for (var j = 0; j < i - 1; j++)
                    {
                        if (String.IsNullOrWhiteSpace(lineList[j])) continue;
                        var nextLine = CheckNextLine(lineList, j + 1);//找到下一条非空数据

                        var length1 = lineList[j].Substring(0, lineList[j].LastIndexOf(';')).Length;//这里处理的目的是为了排除注释类的干扰到排序
                        var length2 = lineList[nextLine].Substring(0, lineList[nextLine].LastIndexOf(';')).Length;
                        if (length1 > length2)//判断 using 引用长度
                        {
                            var tempItem = lineList[j];
                            lineList[j] = lineList[nextLine];
                            lineList[nextLine] = tempItem;
                        }
                    }
                }

                #endregion

                #region 重新写入

                var allLineList = System.IO.File.ReadAllLines(item, encoding);//所有行集合

                for (var i = 0; i < lineList.Count; i++)
                {
                    allLineList[i + line] = lineList[i];//将已经修改后的行，对应到现有行号进行修改
                }

                var list = allLineList.ToList();//将 String 数组转换为 List<String> 以便删除其中元素

                for (var i = 0; i < list.Count; i++)
                {
                    lineText = list[i];

                    if (CheckUsingIsEnd(lineText)) break;//出现Namespace通常意味着Using结束了，若遇到神奇的那就是缘份了

                    if (String.IsNullOrWhiteSpace(lineText) && !CheckUsingIsEnd(list[i + 1]) && !list[i + 1].StartsWith("// ")) //同时再验证下一行
                        list.RemoveAt(i);//移除命名空间中的空行
                }

                try
                {
                    System.IO.File.WriteAllLines(item, list.ToArray(), encoding);//重新写入，排序完成
                }
                catch (UnauthorizedAccessException ex)
                {
                    ShowCurrentItem(String.Format("Access to the file '{0}' is denied.", fileName));
                    Thread.Sleep(2000);
                }

                #endregion

                ShowCurrentItem("Sort Using Complete");//显示状态
            }
        }

        #endregion

        #region Private Method

        /// <summary>
        /// 验证WinRar是否安装。
        /// </summary>
        /// <returns>true：已安装，false：未安装</returns>
        private bool ExistsWinRar(out String winRarPath)
        {
            winRarPath = String.Empty;

            //通过Regedit（注册表）找到WinRar文件 
            var regeditWinRar = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\WinRAR.exe");

            if (regeditWinRar != null)//验证是否安装WinRar
            {
                //regeditWinRar = theReg;可以直接返回Registry对象供会面操作
                winRarPath = regeditWinRar.GetValue("").ToString(); //这里为节约资源，直接返回路径，反正下面也没用到
                regeditWinRar.Close();
                regeditWinRar.Dispose(); //关闭注册表
            }
            //else
            //{
            //    var regeditHaoZip = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\HaoZip.exe");
            //    if (regeditHaoZip == null) return false;
            //    winRarPath = regeditHaoZip.GetValue("").ToString(); //这里为节约资源，直接返回路径，反正下面也没用到
            //    regeditHaoZip.Close(); //关闭注册表
            //    regeditHaoZip.Dispose();
            //}

            return !String.IsNullOrEmpty(winRarPath);
        }

        /// <summary>
        /// 获取 Class 文件列表
        /// </summary>
        /// <param name="dir">目标路径</param>
        /// <param name="list">Class 列表</param>
        private void GetClassList(String dir, ref  List<String> list)
        {
            var source = new DirectoryInfo(dir);//目标路径（项目所在路径）

            var files = source.GetFiles();//获取文件

            var dirs = source.GetDirectories();//返回路径信息

            foreach (DirectoryInfo info in dirs)
            {
                GetClassList(info.FullName, ref list);//递归寻找文件夹下面的
            }

            foreach (var fileInfo in files)
            {
                if (fileInfo.Extension == ".cs")
                    list.Add(fileInfo.FullName);//记录Class文件
            }
        }

        /// <summary>
        /// 寻找下一个非空的行号
        /// </summary>
        /// <param name="lineList">集合</param>
        /// <param name="line">行号</param>
        /// <returns></returns>
        private Int32 CheckNextLine(List<String> lineList, Int32 line)
        {
            return String.IsNullOrWhiteSpace(lineList[line])
                ? CheckNextLine(lineList, line + 1)
                : line;
        }

        /// <summary>
        /// 判断Using引用是否结束
        /// </summary>
        /// <param name="str"></param>
        /// <returns></returns>
        private Boolean CheckUsingIsEnd(String str)
        {
            return str.StartsWith("namespace ") || str.StartsWith("[assembly:");
        }

        /// <summary>
        /// 获取文件编码格式
        /// </summary>
        /// <param name="fileName">文件名</param>
        /// <returns></returns>
        public Encoding GetFileEncodeType(string fileName)
        {
            var fs = new FileStream(fileName, FileMode.Open, FileAccess.Read);
            var br = new BinaryReader(fs);
            Byte[] buffer = br.ReadBytes(2);
            if (buffer[0] < 0xEF) return Encoding.Default;

            if (buffer[0] == 0xEF && buffer[1] == 0xBB)// 239   187
            {
                return Encoding.UTF8;
            }
            if (buffer[0] == 0xFE && buffer[1] == 0xFF)
            {
                return Encoding.BigEndianUnicode;
            }
            if (buffer[0] == 0xFF && buffer[1] == 0xFE)
            {
                return Encoding.Unicode;
            }
            return Encoding.Default;
        }

        #endregion
    }
}
